package com.rgbgame.editor;

import processing.core.PApplet;
import processing.core.PFont;

import javax.swing.*;
import java.io.File;
import java.util.ArrayList;

import com.rgbgame.game.Player;
import com.rgbgame.game.Vector3d;
import com.rgbgame.launcher.Launcher;

public class rgbEditor {

    /** static singleton **/
    public static rgbEditor editor;
    /** static singleton **/
    public static Launcher pApplet;
    
    /**
     * Level Editor for RGB game.
     * <p/>
     * Instructions forthcoming...
     * Output should ultimately be a plaintext string that specifies a level in RGB format.
     * Still undecided how this should be saved/forwarded to the game.
     * Also need to port the parser so that this applet can load the same files for editing.
     * <p/>
     * Created 29 March 2007
     */



    /* Todo:
    - Remove duplicate lines and points (both the cases where two lines have the same particles and where multiple particles lie in the same exact spot)
      (May be happening if you go back over lines you've already drawn, not sure) [CHECKED - it draws more lines, but not more points]
    - Snap-to-line mode (create new particle on line) [might not be very useful]
    - Pencil mode

      */

//	All non-UI drawing should go through viewport
    Viewport myView;

    //	com.rgbgame.editor.Point type identifiers - may want to add in coloring info here, too
    public static final int NONE = 0;
    public static final int WALL = 1;
    public static final int PARTICLE = 2;
    public static final int PINPARTICLE = 3;
    public static final int TEMP = 4;

//	com.rgbgame.editor.Line type identifiers
//	NONE is still 0
    //	WALL is still 1 (normal wall)
    public static final int STRUT = 2;
    public static final int SCENERY = 3;
    //	TEMP is still 4
    public static final int ICEWALL = 5;
    public static final int DEATHWALL = 6;
    public static final int STICKYWALL = 7;
    public static final int SOLIDSTRUT = 8;

    public static final int RED = 1;
    public static final int GREEN = 2;
    public static final int BLUE = 3;
    public static final int INVISIBLE = 4;

    int pointType = WALL;
    int pointColor = NONE;
    int lineType = WALL;
    int lineColor = NONE;

    int wallType = WALL;

    int BGCOLOR;

    int lastTool;
    public static int toolType;

    //	arrays of objects that we create
    ArrayList myPoints;
    ArrayList myLines;
    ArrayList myParticles; //particles are also in points list

    //	Vars
    float moveSpeed = 10.0f;
    int lineIndex = 0; //current line indexing number - keeps us from having to re-index upon deletion
    int pointIndex = 0; //current point indexing number
    int particleIndex = 0; //particle indexing number
    
    // Stored header information
    float playerX, playerY;
    float goalX, goalY;
    float gravity;
    float springConstant;
    float maxAngularVelocity;
    float maxAbsSpin;
    float angularAcceleration;

    FileHandler myLoader;


    public void setup(Launcher applet, String levelString) {
        pApplet = applet;
        editor = this;
        customInit(levelString, false);
    }

    public void setup(Launcher applet) {
        pApplet = applet;
        editor = this;
        customInit("", true);
    }

    public void customInit(String levelString, boolean levelFromFile) {
        //Initialization code here - can't call setup() twice, so anything important MUST be here so we can reset program while running
        lineIndex = 0;
        pointIndex = 0;
        particleIndex = 0;
        lineType = WALL;
        wallType = WALL;
        pointType = WALL;

        lastTool = TOOL_NONE;
        toolType = TOOL_LINE;
        BGCOLOR = pApplet.color(150, 150, 150);
        myView = new Viewport();
        myPoints = new ArrayList();
        myLines = new ArrayList();
        myParticles = new ArrayList();
        lineIndex = 0;
        pointIndex = 0;
        particleIndex = 0;
        // Set header defaults
        playerX = 100.0f;
        playerY = 100.0f;
        goalX = 500.0f;
        goalY = 100.0f;
        gravity = 1700.0f;
        springConstant = 1900.0f;
        maxAngularVelocity = 10000.0f;
        maxAbsSpin = 12000.0f;
        angularAcceleration = .002f;
        UIInit();
        if (levelString.length() > 0) myLoader = new FileHandler(this, levelString, levelFromFile);
            else myLoader = new FileHandler(this);
    }

    public void draw() {
        //println("frame");
        pApplet.background(BGCOLOR);
        pApplet.stroke(255);  //Note: we should really set stroke() before each line that we draw, otherwise ordering issues pop up
        //if (!UITrap()){
        //If there is a UI event, trap it and don't handle keys or mouse - don't want to draw lines when user is trying to switch tools!
        setCursor();
        handleKeys();
        handleMouse(pApplet.mouseX, pApplet.mouseY, pApplet.pmouseX, pApplet.pmouseY, pApplet.mousePressed, pApplet.mouseButton);
        //}

        drawPlayer();
        drawGoal();
        drawAllLines();
        drawAllPoints();
//		pushMatrix();
//		translate(0,0,1); //draw the UI slightly closer to the camera than the rest of the stuff so that it appears on top
        UIDraw();
//		popMatrix();

    }
    
    public void drawPlayer(){
    	Vector2d playerPos = new Vector2d(playerX, playerY);
    	pApplet.fill(0.0f,255.0f,0.0f,100.0f);
    	pApplet.noStroke();
    	drawCircle(playerPos.x, playerPos.y, 30.0f * myView.scaling);
    }
    
    public void drawGoal(){
    	Vector2d goalPos = new Vector2d(goalX, goalY);
    	pApplet.fill(0.0f,0.0f,255.0f,100.0f);
    	pApplet.noStroke();
    	drawCircle(goalPos.x, goalPos.y, 40.0f * myView.scaling);
    }


    public String print() {
        System.out.println("Beginning level output:");
        String out = "";
        out += printHeader();
        out += printAllWalls();
        out += printAllParticles();
        out += printAllStruts();
        System.out.println("End of level output.");
        return out;
        //printAllPoints();
    }

    public String printHeader() {
    	return myLoader.generateHeader();
        //return myLoader.printHeader();
    }

    public String printAllWalls() {
        //println("lines:");
        String out = "";
        for (int i = 0; i < myLines.size(); i++) {
            if (isWall(getLineByIndex(i).type)) {
                System.out.println(getLineByIndex(i).toString());
                out += getLineByIndex(i).toString() + "\n";
            }
        }
        return out;
    }

    private boolean isWall(int num) {
        if (num == WALL || num == ICEWALL || num == DEATHWALL || num == STICKYWALL) {
            return true;
        } else return false;
    }

    public String printAllParticles() {
        String out = "particlesBegin\n";
        System.out.println("particlesBegin");
        float pMass = 1.0f;
        float pRad = 1.0f;
        for (int i = 0; i < myParticles.size(); i++) {
            Point myP = getParticleByIndex(i);
            String myS = (myP.type == PARTICLE) ? "particle," : "pinParticle,";
            myS += myP.pos.x + "," + myP.pos.y + ",";
            myS += pMass + "," + pRad + ",";
            if (myP.color == RED) myS += "RED";
            else if (myP.color == GREEN) myS += "GREEN";
            else if (myP.color == BLUE) myS += "BLUE";
            else myS += "NONE";
            System.out.println(myS);
            out += myS + "\n";
        }
        System.out.println("particlesEnd");
        out += "particlesEnd\n";
        return out;
    }

    public String printAllStruts() {
        String out = "strutsBegin\n";
        System.out.println("strutsBegin");
        for (int i = 0; i < myLines.size(); i++) {
            Line myL = getLineByIndex(i);
            if (myL.type == STRUT) {
                String myS = "strut,";
                myS += myL.pA.particleIndex + "," + myL.pB.particleIndex + ",";
                if (myL.color == RED) myS += "RED";
                else if (myL.color == GREEN) myS += "GREEN";
                else if (myL.color == BLUE) myS += "BLUE";
                else myS += "INVISIBLE";
                println(myS);
                out += myS + "\n";
            }
            if (myL.type == SOLIDSTRUT) {
                String myS = "strut,";
                myS += myL.pA.particleIndex + "," + myL.pB.particleIndex + ",";
                myS += "COLLIDING";
                println(myS);
                out += myS + "\n";
            }
        }
        out += "strutsEnd\n";
        println("strutsEnd");
        return out;
    }

    private void println(String myS) {
        System.out.println(myS);
    }

    public void printAllPoints() {
        println("points:");
        for (int i = 0; i < myPoints.size(); i++) {

            println(getPointByIndex(i).toString());
        }
    }


    public void drawAllLines() {
        for (int i = 0; i < myLines.size(); i++) {
            getLineByIndex(i).draw();
        }
    }

    public void drawAllPoints() {
        for (int i = 0; i < myPoints.size(); i++) {

            getPointByIndex(i).draw();
        }
    }

    public void drawCircle(float x, float y, float r) {
        //Draws a circle with radius r at world point x, y- IN PIXELS, NOT WORLD COORDINATES!!!
        Vector2d trans = transform(x, y);
        pApplet.ellipse(trans.x, trans.y, 2 * r, 2 * r);
    }

    public void drawLine(float x1, float y1, float x2, float y2) {
        //Draws a line to the screen using the transform function to bring it from world to screen coordinates.
        //For UI stuff, probably best to use the regular old line() function
        Vector2d trans1 = transform(x1, y1);
        Vector2d trans2 = transform(x2, y2);
        pApplet.line(trans1.x, trans1.y, trans2.x, trans2.y);
        //println(trans1.x+" "+trans1.y+" "+trans2.x+" "+trans2.y);
    }

    public void drawRect(float x1, float y1, float x2, float y2) {
        //Draws a rectangle outline - note that the x,y pairs mark the corners, NOT the height and width (which is different from rect() in Processing)
        drawLine(x1, y1, x2, y1);
        drawLine(x2, y1, x2, y2);
        drawLine(x2, y2, x1, y2);
        drawLine(x1, y2, x1, y1);
    }

    public void boxOrder(Vector2d minV, Vector2d maxV) {
        Vector2d buffer = new Vector2d(minV.x, minV.y);
        minV.x = PApplet.min(minV.x, maxV.x);
        minV.y = PApplet.min(minV.y, maxV.y);
        maxV.x = PApplet.max(buffer.x, maxV.x);
        maxV.y = PApplet.max(buffer.y, maxV.y);
    }

    public boolean contains(Vector2d minVec, Vector2d maxVec, Vector2d test) {
        //Does it contain the point "test"?
        boxOrder(minVec, maxVec);
        if (test.x > minVec.x && test.x < maxVec.x &&
                test.y > minVec.y && test.y < maxVec.y) {
            return true;
        } else {
            return false;
        }
    }

    public boolean hitBy(Vector2d minVec, Vector2d maxVec, Vector2d lineA, Vector2d lineB) {
        //Is the box specified by minVec->maxVec pierced by the line segment between lineA and lineB?
        boxOrder(minVec, maxVec);
        //First some early exit stuff
        if (lineA.x > maxVec.x && lineB.x > maxVec.x) {
            return false;
        }
        if (lineA.x < minVec.x && lineB.x < minVec.x) {
            return false;
        }
        if (lineA.y > maxVec.y && lineB.y > maxVec.y) {
            return false;
        }
        if (lineA.y < minVec.y && lineB.y < minVec.y) {
            return false;
        }
        if (contains(minVec, maxVec, lineA) || contains(minVec, maxVec, lineB)) {
            return true;
        } //quick test, get cases where one or both points is in box
        else
        if (lineCross(lineA, lineB, minVec, maxVec) || lineCross(lineA, lineB, new Vector2d(minVec.x, maxVec.y), new Vector2d(maxVec.x, minVec.y))) {
            return true;
        } //otherwise, containment iff line crosses diagonal
        else {
            return false;
        } //no pierce
    }

    public boolean lineCross(Vector2d wallA, Vector2d wallB, Vector2d lineA, Vector2d lineB) {
        //Do the two lines wallA->wallB and lineA->lineB cross?
        float x1 = lineA.x;
        float y1 = lineA.y;
        float x2 = lineB.x;
        float y2 = lineB.y;
        float x3 = wallA.x;
        float y3 = wallA.y;
        float x4 = wallB.x;
        float y4 = wallB.y;

        float ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3));
        float ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3));
        float denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
        if (PApplet.abs(denom) < .0001f) {
            return false; //skip town if things get tough
        }
        ua /= denom;
        ub /= denom;

        if ((0 < ua) && (ua < 1) && (0 < ub) && (ub < 1)) {
            return true;
        }

        return false;
    }


    boolean keyState[] = new boolean[255];
    boolean UPARROW = false;
    boolean DOWNARROW = false;
    boolean LEFTARROW = false;
    boolean RIGHTARROW = false;
    boolean CTRL = false;
    boolean SHFT = false;

    float maxZoomSpeed = .04f;
    float currentZoomSpeed = .00f;
    float zoomAccel = .02f;

    public void handleKeys() {
        if (keyState['r']) {
            lineColor = RED;
            pointColor = RED;
        }
        if (keyState['g']) {
            lineColor = GREEN;
            pointColor = GREEN;
        }
        if (keyState['b']) {
            lineColor = BLUE;
            pointColor = BLUE;
        }
        if (keyState['i']) {
            lineColor = INVISIBLE;
        }
        if (keyState['1']) {
            lineType = WALL;
            wallType = WALL;
            pointType = WALL;
        }
        if (keyState['2']) {
            lineType = STRUT;
            pointType = PARTICLE;
        }
        if (keyState['3']) {
            lineType = STRUT;
            pointType = PINPARTICLE;
        }
        if (keyState['4']) {
            lineType = WALL;
            wallType = ICEWALL;
            pointType = WALL;
        }
        if (keyState['5']) {
            lineType = WALL;
            wallType = DEATHWALL;
            pointType = WALL;
        }
        if (keyState['6']) {
            lineType = WALL;
            wallType = STICKYWALL;
            pointType = WALL;
        }
        if (keyState['7']) {
            lineType = SOLIDSTRUT;
            pointType = PARTICLE;

        }
        if (keyState['e']) {
            //println("E!");
            myView.nudgeZoom(currentZoomSpeed);
            currentZoomSpeed = (1.0f - zoomAccel) * currentZoomSpeed + zoomAccel * maxZoomSpeed;
        } else if (keyState['s']) {
            println("done");
            print();
        } else if (keyState['q']) {
            myView.nudgeZoom(-currentZoomSpeed);
            currentZoomSpeed = (1.0f - zoomAccel) * currentZoomSpeed + zoomAccel * maxZoomSpeed;
        } else {
            currentZoomSpeed = 0;
        }
        if (UPARROW) {
            myView.scaledNudge(new Vector2d(0, -moveSpeed));
        }
        if (DOWNARROW) {
            myView.scaledNudge(new Vector2d(0, moveSpeed));
        }
        if (RIGHTARROW) {
            myView.scaledNudge(new Vector2d(moveSpeed, 0));
        }
        if (LEFTARROW) {
            myView.scaledNudge(new Vector2d(-moveSpeed, 0));
        }
        if (keyState['o']) {
            chooseFile();
            keyState['o'] = false;
        }
        if(keyState[';']) {
            keyState[';'] = false;
            playLevel();
        }
    }

    private void playLevel() {
        System.out.println("switching to game!");
        pApplet.launch(Launcher.GAME);
    }

    public void keyPressed(int keyCode, int key) {
        if (keyCode == PApplet.CONTROL) {
//				println("ctrl pressed");
            CTRL = true;
        }
        if (keyCode == PApplet.SHIFT) {
            SHFT = true;
        }
        if (keyCode == PApplet.UP) {
            UPARROW = true;
        }
        if (keyCode == PApplet.DOWN) {
            DOWNARROW = true;
        }
        if (keyCode == PApplet.LEFT) {
            LEFTARROW = true;
        }
        if (keyCode == PApplet.RIGHT) {
            RIGHTARROW = true;
        }
        /*
              if ((key == 'e' || key == 'E')){
                  keyState['e'] = true;
              }
              if ((key == 'q' || key == 'Q')){
                  keyState['q'] = true;
              }
              if ((key == 'r' || key == 'R')){
                  keyState['r'] = true;
              }

              if ((key == 'g' || key == 'G')){
                  keyState['g'] = true;
              }

              if ((key == 'b' || key == 'B')){
                  keyState['b'] = true;
              }

              if ((key == 'd' || key == 'D')){
                  keyState['d'] = true;
              }*/
        if (key > 0 && key < 256) keyState[key] = true;
    }

    public void keyReleased(int keyCode, int key) {
        if (keyCode == PApplet.CONTROL) {
            CTRL = false;
        }
        if (keyCode == PApplet.SHIFT) {
            SHFT = false;
        }
        if (keyCode == PApplet.UP) {
            UPARROW = false;
        }
        if (keyCode == PApplet.DOWN) {
            DOWNARROW = false;
        }
        if (keyCode == PApplet.LEFT) {
            LEFTARROW = false;
        }
        if (keyCode == PApplet.RIGHT) {
            RIGHTARROW = false;
        }
        /*
              if ((key == 'e' || key == 'E')){
                  keyState['e'] = false;
              }
              if ((key == 'q' || key == 'Q')){
                  keyState['q'] = false;
              }
              if ((key == 'r' || key == 'R')){
                  keyState['r'] = false;
              }

              if ((key == 'g' || key == 'G')){
                  keyState['g'] = false;
              }
              if ((key == 'b' || key == 'B')){
                  keyState['b'] = false;
              }
              if ((key == 'd' || key == 'D')){
                  keyState['d'] = false;
              }*/
        if (key > 0 && key < 256) keyState[key] = false;

    }

    /**
     * These functions handle the point and line arrays
     * and provide useful methods for getting, adding,
     * and deleting points and lines using different
     * criteria for selection.
     * <p/>
     * Warning: many of these methods are going to be
     * very slow for large arrays of points and lines.
     * Future optimizations may be necessary to achieve
     * real time speed, though this is less crucial in
     * the editor than in the game (which should be more
     * highly optimized).
     */

//		BUGFIX: some of these throw exceptions (potentially), should add checking
    public void addPoint(Point myPoint) {
        myPoints.add(myPoint);
        myPoint.index = pointIndex++;
        if (myPoint.type == PARTICLE || myPoint.type == PINPARTICLE) {
            myParticles.add(myPoint);
            myPoint.particleIndex = particleIndex++;
        }
        myPoint.color = pointColor;
        println(pointIndex + " points");
    }

    public void addPoint(Vector2d vec, int type) {
        addPoint(new Point(vec, type));
    }

    public void addPoint(float x, float y, int type) {
        addPoint(new Vector2d(x, y), type);
    }

    public void addLine(Point pA, Point pB, int type) {
        if (pA.index == -1) {
            addPoint(pA);
        }
        if (pB.index == -1) {
            addPoint(pB);
        }
        //The following if tests ensure that the types of the particles
        //are compatible with the types of the wall, and create new points
        //if they are not.
        if (pA.type == WALL && type == STRUT) {
            pA = new Point(pA.pos, PINPARTICLE);
            addPoint(pA);
        }
        if (pB.type == WALL && type == STRUT) {
            pB = new Point(pB.pos, PINPARTICLE);
            addPoint(pB);
        }
        if (type == WALL && (pA.type == PARTICLE || pA.type == PINPARTICLE)) {
            pA = new Point(pA.pos, WALL);
            addPoint(pA);
        }
        if (type == WALL && (pB.type == PARTICLE || pB.type == PINPARTICLE)) {
            pB = new Point(pB.pos, WALL);
            addPoint(pB);
        }
        Line myLine = new Line(pApplet, pA, pB, type);
        if (type == WALL) myLine.type = wallType;
        if (type != WALL) {
            //println("hi");
            myLine.color = lineColor;
        }
        //println(lineType+" "+type);
        myLines.add(myLine);
        myLine.index = lineIndex++;
        println(lineIndex + " lines");
    }

    public Line getLineByIndex(int i) {
        return (Line) myLines.get(i);
    }

    public Point getPointByIndex(int i) {
        return (Point) myPoints.get(i);
    }

    public Point getParticleByIndex(int i) {
        return (Point) myParticles.get(i);
    }

    public Line[] getLinesByPoint(Point p) {
        //Returns each line connected to a point
        //int index = p.index;
        int numInRange = 0;
        for (int i = 0; i < myLines.size(); i++) {
            Line myLine = getLineByIndex(i);
            if (myLine.pA.index == p.index || myLine.pB.index == p.index) {
                numInRange++;
            }
        }
        Line[] result = new Line[numInRange];
        int currIndex = 0;
        for (int i = 0; i < myLines.size(); i++) {
            Line myLine = getLineByIndex(i);
            if (myLine.pA.index == p.index || myLine.pB.index == p.index) {
                result[currIndex] = myLine;
                currIndex++;
            }
        }
        return result;
    }

    public Point getPointByLocation(Vector2d loc) {
        if (myPoints.size() == 0) {
            return null;
        }
        //Returns nearest point to loc
        float minDist = 10000000.0f; //hopefully actual distance is less than this for all points...otherwise, you're making a really ridiculous level that will not run anyways!
        int minIndex = -1;
        for (int i = 0; i < myPoints.size(); i++) {
            Point myPoint = getPointByIndex(i);
            float myDistance = loc.distanceTo(myPoint.pos);
            if (myDistance < minDist) {
                minDist = myDistance;
                minIndex = i;
            }
        }
        return getPointByIndex(minIndex);
    }

    public Point[] getPointsInRectangle(Vector2d rA, Vector2d rB) {
        Vector2d minV = new Vector2d(PApplet.min(rA.x, rB.x), PApplet.min(rA.y, rB.y));
        Vector2d maxV = new Vector2d(PApplet.max(rA.x, rB.x), PApplet.max(rA.y, rB.y));
        int numInRange = 0;
        for (int i = 0; i < myPoints.size(); i++) {
            Point myPoint = getPointByIndex(i);
            if (myPoint.pos.x > minV.x && myPoint.pos.x < maxV.x &&
                    myPoint.pos.y > minV.y && myPoint.pos.y < maxV.y) {
                numInRange++;
            }
        }
        Point[] result = new Point[numInRange];
        int currIndex = 0;
        for (int i = 0; i < myPoints.size(); i++) {
            Point myPoint = getPointByIndex(i);
            if (myPoint.pos.x > minV.x && myPoint.pos.x < maxV.x &&
                    myPoint.pos.y > minV.y && myPoint.pos.y < maxV.y) {
                result[currIndex] = myPoint;
                currIndex++;
            }
        }
        return result;
    }

    public Line[] getLinesInRectangle(Vector2d rA, Vector2d rB) {
        Vector2d minV = new Vector2d(PApplet.min(rA.x, rB.x), PApplet.min(rA.y, rB.y));
        Vector2d maxV = new Vector2d(PApplet.max(rA.x, rB.x), PApplet.max(rA.y, rB.y));
        Vector2d ur = new Vector2d(maxV.x, minV.y);
        Vector2d ll = new Vector2d(minV.x, maxV.y);
        int numInRange = 0;
        for (int i = 0; i < myLines.size(); i++) {
            Line myLine = getLineByIndex(i);
            if (lineCross(minV, maxV, myLine.pA.pos, myLine.pB.pos) ||
                    lineCross(ur, ll, myLine.pA.pos, myLine.pB.pos) ||
                    contains(minV, maxV, myLine.pA.pos) ||
                    contains(minV, maxV, myLine.pB.pos)) {
                numInRange++;
            }
        }
        Line[] result = new Line[numInRange];
        int currIndex = 0;
        for (int i = 0; i < myLines.size(); i++) {
            Line myLine = getLineByIndex(i);
            if (lineCross(minV, maxV, myLine.pA.pos, myLine.pB.pos) ||
                    lineCross(ur, ll, myLine.pA.pos, myLine.pB.pos) ||
                    contains(minV, maxV, myLine.pA.pos) ||
                    contains(minV, maxV, myLine.pB.pos)) {
                result[currIndex] = myLine;
                currIndex++;
            }
        }
        return result;
    }

    public void deletePoint(Point myPoint) {
        if (myPoint.index >= 0) {
            deletePointByIndex(myPoint.index);
        }
//			Need to set up undo stack and wind all this stuff into it
    }

    public void deletePointByIndex(int ind) {
        if (ind < myPoints.size() && ind >= 0) {
            Point myP = (Point) myPoints.get(ind);
            Line[] myL = getLinesByPoint(myP);
            for (int i = 0; i < myL.length; i++) {
                deleteLine(myL[i]);
            }
            myPoints.remove(ind);
            if (myP.particleIndex > -1) {
                myParticles.remove(myP.particleIndex);
            }
        }
        fixIndexing();
        println(pointIndex + " points");
    }

    public void deleteLine(Line myLine) {
        if (myLine.index >= 0) {
            deleteLineByIndex(myLine.index);
        }
    }

    public void deleteLineByIndex(int ind) {
        if (ind < myLines.size() && ind >= 0) {
            myLines.remove(ind);
        }
        fixIndexing();
        println(lineIndex + " lines");
    }

    private void fixIndexing() {
        //makes sure all index numbers are correct, must be called after any deletion
        for (int i = 0; i < myPoints.size(); i++) {
            ((Point) myPoints.get(i)).index = i;
        }
        pointIndex = myPoints.size();
        for (int i = 0; i < myLines.size(); i++) {
            ((Line) myLines.get(i)).index = i;
        }
        lineIndex = myLines.size();
        for (int i = 0; i < myParticles.size(); i++) {
            ((Point) myParticles.get(i)).particleIndex = i;
        }
        particleIndex = myParticles.size();
    }

    public void deleteInRectangle(float x1, float y1, float x2, float y2) {
        deleteInRectangle(new Vector2d(x1, y1), new Vector2d(x2, y2));
    }

    public void deleteInRectangle(Vector2d v1, Vector2d v2) {
        Point[] delPoints = getPointsInRectangle(v1, v2);
        ;
        Line[] delLines = getLinesInRectangle(v1, v2);
        //Necessary to delete lines first, I think
        for (int i = 0; i < delLines.length; i++) {
            deleteLine(delLines[i]);
        }
        for (int i = 0; i < delPoints.length; i++) {
            deletePoint(delPoints[i]);
        }
    }


    //		Tool types
    public static final int TOOL_NONE = 0;
    public static final int TOOL_HAND = 1;
    public static final int TOOL_MAG = 2;
    public static final int TOOL_LINE = 3;
    public static final int TOOL_ERASER = 4;
    public static final int TOOL_POINT = 5;
    public static final int TOOL_PENCIL = 6;
    public static final int TOOL_LINE_STRIP = 7;
    public static final int TOOL_ARROW = 8;

    //int pointType = NONE;

    boolean lastPressed;
    int lastButton;
    float eraserWidth = 15f;

    float pencilMinLineDistance = 5f;
    float pencilDotProduct = .9f;
    float pencilMaxLineDistance = 20f;
    Vector2d pencilLastPoint = null;
    Vector2d pencilNextLastPoint = null;

    Point arrowGrabPoint = null;

    public void handleMouse(int mouseX, int mouseY, int pmouseX, int pmouseY, boolean mousePressed, int mouseButton) {

        if (toolType == TOOL_ERASER) {
            pApplet.rect(mouseX - eraserWidth / 2.0f, mouseY - eraserWidth / 2.0f, eraserWidth, eraserWidth);
        }
        if (mousePressed && mouseButton == PApplet.LEFT && !UITrap()) {
            focusLock = EDITOR;

            if (SHFT && !draggingLine) { //shift + drag grabs the canvas and moves it
                myView.scaledNudge(new Vector2d(-mouseX + pmouseX, -mouseY + pmouseY));
                return;
            }
            switch (toolType) {
                case TOOL_NONE:
                    break;
                case TOOL_ERASER:
//					if (SHFT){
//						myView.scaledNudge(new com.rgbgame.editor.Vector2d(-mouseX+pmouseX,-mouseY+pmouseY));
//					}
//					else{
                    Vector2d eraseRectA = inverseTransform(new Vector2d(mouseX - eraserWidth / 2.0f, mouseY - eraserWidth / 2.0f));
                    Vector2d eraseRectB = inverseTransform(new Vector2d(mouseX + eraserWidth / 2.0f, mouseY + eraserWidth / 2.0f));
                    deleteInRectangle(eraseRectA, eraseRectB);
//					}
                    break;
                case TOOL_MAG:
//					if (SHFT){
//						myView.scaledNudge(new com.rgbgame.editor.Vector2d(-mouseX+pmouseX,-mouseY+pmouseY));
//					}
//					else{
                    myView.nudgeZoom((pmouseY - mouseY) * .01f);
                    //println(pmouseY-mouseY);
//					}
                    break;
                case TOOL_ARROW:
//					if (SHFT){ //shift + drag grabs the canvas and moves it
//						myView.scaledNudge(new com.rgbgame.editor.Vector2d(-mouseX+pmouseX,-mouseY+pmouseY));
//						break;
//					}
                    Vector2d amouseWorld = inverseTransform(new Vector2d(mouseX, mouseY));
                    if (arrowGrabPoint == null) {
                    	if (amouseWorld.distanceTo(new Vector2d(playerX,playerY)) < 30.0f) {
                    		playerX = amouseWorld.x;
                    		playerY = amouseWorld.y;
                    		break;
                    	} else if (amouseWorld.distanceTo(new Vector2d(goalX,goalY)) < 40.0f) {
                    		goalX = amouseWorld.x;
                    		goalY = amouseWorld.y;
                    		break;
                    	}
                        Point anearestPoint = getPointByLocation(amouseWorld);
                        if (anearestPoint != null) {
                            Vector2d pointScreen = transform(anearestPoint.pos);
                            if (pointScreen.distanceTo(new Vector2d(mouseX, mouseY)) < anearestPoint.pointRadius) {
                                arrowGrabPoint = anearestPoint;
                                arrowGrabPoint.pos.set(amouseWorld.x, amouseWorld.y);
                            }
                        }
                    } else {
                        arrowGrabPoint.pos.set(amouseWorld.x, amouseWorld.y);
                    }
                    break;
                case TOOL_POINT:
//					if (SHFT){ //shift + drag grabs the canvas and moves it
//						myView.scaledNudge(new com.rgbgame.editor.Vector2d(-mouseX+pmouseX,-mouseY+pmouseY));
//					}
//					else 
                    if (!draggingLine && (mousePressed != lastPressed)) {
                        Vector2d mouseWorld = inverseTransform(new Vector2d(mouseX, mouseY));
                        addPoint(mouseWorld.x, mouseWorld.y, pointType);
                    }
                    break;
                case TOOL_HAND:
                    myView.scaledNudge(new Vector2d(-mouseX + pmouseX, -mouseY + pmouseY));
                    break;
                case TOOL_PENCIL: //TOOL_PENCIL needs to be written, but I need to think about how to make it not draw too many lines
                    //TODO
                    if (!draggingLine && (mousePressed != lastPressed)) {
                        //We have a new click
                        pencilLastPoint = null;
                        pencilNextLastPoint = null;
                        draggingLine = true;
                        Vector2d mouseWorld = inverseTransform(new Vector2d(mouseX, mouseY));
                        Point nearestPoint = getPointByLocation(mouseWorld);
                        if (nearestPoint != null) {
                            Vector2d pointScreen = transform(nearestPoint.pos);
                            if (pointScreen.distanceTo(new Vector2d(mouseX, mouseY)) < nearestPoint.pointRadius) {
                                snappedToStartPoint = true;
                                snappedStartPoint = nearestPoint;
                                tempLine.pA = snappedStartPoint;
                            } else {
                                snappedToStartPoint = false;
                                lineStart = mouseWorld;
                                tempLine.pA.pos = lineStart;
                            }
                        } else {
                            snappedToStartPoint = false;
                            lineStart = mouseWorld;
                            tempLine.pA.pos = lineStart;
                        }
                        pencilLastPoint = mouseWorld.cloneVec();
                        //println("Starting line");
                    } else if (draggingLine) {
                        //We are continuing to draw
                        snappedToEndPoint = false;
                        tempLine.pB.pos = inverseTransform(new Vector2d(mouseX, mouseY));
                        if (pencilLastPoint != null) {
                            float newdx = tempLine.pB.pos.x - pencilLastPoint.x;
                            float newdy = tempLine.pB.pos.y - pencilLastPoint.y;
                            float normnew = (float) Math.sqrt(newdx * newdx + newdy * newdy);
                            if (normnew > pencilMaxLineDistance) {
                                //Create line, but project to correct distance first

                                float mult = pencilMaxLineDistance / normnew;

                                tempLine.pB.pos.set(pencilLastPoint.x + newdx * mult, pencilLastPoint.y + newdy * mult);

                                pencilLastPoint = tempLine.pB.pos.cloneVec();
                                pencilNextLastPoint = tempLine.pA.pos.cloneVec();

                                realizeTempLine(pointType, lineType);
                                snappedToStartPoint = true;
                                draggingLine = true;

                                //com.rgbgame.editor.Vector2d mouseWorld =  inverseTransform(new com.rgbgame.editor.Vector2d(mouseX,mouseY));
                                //snappedToStartPoint = false;
                                //lineStart = mouseWorld;
                                //tempLine.pA.pos = lineStart;
                            }
                        }
                        if (pencilLastPoint != null && pencilNextLastPoint != null) {
                            //decide whether to create this point based on angle and distance
                            //println("hi");
                            float lastdx = pencilLastPoint.x - pencilNextLastPoint.x;
                            float lastdy = pencilLastPoint.y - pencilNextLastPoint.y;
                            float newdx = tempLine.pB.pos.x - pencilLastPoint.x;
                            float newdy = tempLine.pB.pos.y - pencilLastPoint.y;
                            float normlast = (float) Math.sqrt(lastdx * lastdx + lastdy * lastdy);
                            float normnew = (float) Math.sqrt(newdx * newdx + newdy * newdy);
                            if (normlast == 0f) {
                                pencilNextLastPoint = null;
                                return;
                            }
                            if (normnew == 0f) return;
                            float lastnx = lastdx / normlast;
                            float lastny = lastdy / normlast;
                            float newnx = newdx / normnew;
                            float newny = newdy / normnew;
                            float dot = lastnx * newnx + lastny * newny;
                            //println(dot);
                            if (dot < pencilDotProduct && normnew > pencilMinLineDistance) {
                                //Create line

                                pencilLastPoint = tempLine.pB.pos.cloneVec();
                                pencilNextLastPoint = tempLine.pA.pos.cloneVec();
                                realizeTempLine(pointType, lineType);
                                snappedToStartPoint = true;
                                draggingLine = true;

//								com.rgbgame.editor.Vector2d mouseWorld =  inverseTransform(new com.rgbgame.editor.Vector2d(mouseX,mouseY));
//								snappedToStartPoint = false;
//								lineStart = mouseWorld;
//								tempLine.pA.pos = lineStart;
                            }
                        }

                        tempLine.draw();
                        if (snappedToStartPoint) {
                            pApplet.fill(255);
                        }
                        tempLine.pA.draw();
                        if (snappedToEndPoint) pApplet.fill(255);
                        else pApplet.noFill();
                        tempLine.pB.draw();
                        pApplet.noFill();
                    }
                    break;
                case TOOL_LINE:
                    if (SHFT) { //shift + drag grabs the canvas and moves it
                        myView.scaledNudge(new Vector2d(-mouseX + pmouseX, -mouseY + pmouseY));
                        if (draggingLine) {
                            tempLine.pB.pos = inverseTransform(new Vector2d(mouseX, mouseY));
                            tempLine.draw();
                        }
                    } else if (!draggingLine && (mousePressed != lastPressed)) {
                        //We have a new click
                        draggingLine = true;
                        Vector2d mouseWorld = inverseTransform(new Vector2d(mouseX, mouseY));
                        Point nearestPoint = getPointByLocation(mouseWorld);
                        if (nearestPoint != null) {
                            Vector2d pointScreen = transform(nearestPoint.pos);
                            if (pointScreen.distanceTo(new Vector2d(mouseX, mouseY)) < nearestPoint.pointRadius) {
                                snappedToStartPoint = true;
                                snappedStartPoint = nearestPoint;
                                tempLine.pA = snappedStartPoint;
                            } else {
                                snappedToStartPoint = false;
                                lineStart = mouseWorld;
                                tempLine.pA.pos = lineStart;
                            }
                        } else {
                            snappedToStartPoint = false;
                            lineStart = mouseWorld;
                            tempLine.pA.pos = lineStart;
                        }
                        //println("Starting line");
                    } else if (draggingLine) {
                        //We have a held click
                        Vector2d mouseWorld = inverseTransform(new Vector2d(mouseX, mouseY));
                        Point nearestPoint = getPointByLocation(mouseWorld);
                        if (nearestPoint != null) {
                            Vector2d pointScreen = transform(nearestPoint.pos);
                            if (pointScreen.distanceTo(new Vector2d(mouseX, mouseY)) < nearestPoint.pointRadius) {
                                snappedToEndPoint = true;
                                snappedEndPoint = nearestPoint;
                                tempLine.pB.pos = new Vector2d(nearestPoint.pos.x, nearestPoint.pos.y);
                            } else {
                                snappedToEndPoint = false;
                                tempLine.pB.pos = inverseTransform(new Vector2d(mouseX, mouseY));
                            }
                        } else {
                            snappedToEndPoint = false;
                            tempLine.pB.pos = inverseTransform(new Vector2d(mouseX, mouseY));
                        }

                        tempLine.draw();
                        if (snappedToStartPoint) {
                            pApplet.fill(255); //bug - this is overridden by any point-specific rendering, and it's
                            //not a good idea in general to set fills outside of drawing code
                        }
                        tempLine.pA.draw();
                        if (snappedToEndPoint) {
                            pApplet.fill(255);
                        } else {
                            pApplet.noFill();
                        }
                        tempLine.pB.draw();
                        pApplet.noFill();
                        //println("Drawing line from "+tempLine.pA.pos.x+","+tempLine.pA.pos.y+" to "+tempLine.pB.pos.x+","+tempLine.pB.pos.y);
                    }
                    break;
            }
        } else { //left mouse button either not pressed or blocked by UI handler

            if (toolType == TOOL_LINE) {
                realizeTempLine(pointType, lineType);
                draggingLine = false;
            } else if (toolType == TOOL_ARROW) {
                arrowGrabPoint = null;
            }
            if (toolType == TOOL_PENCIL) {
                //Create line
                realizeTempLine(pointType, lineType);
                clearDraggedLine();
                //draggingLine = false;
                //pencilLastPoint = null;
                //pencilNextLastPoint = null;
            }

            focusLock = NONE;

        }

        //Here handle the special case of a line strip, which handles clicks a bit differently
        if (toolType == TOOL_LINE_STRIP) {
            focusLock = NONE;
            if (UITrap()) {
            } else if (SHFT && mousePressed && mouseButton == PApplet.LEFT) { //shift + drag grabs the canvas and moves it
                focusLock = EDITOR;
                myView.scaledNudge(new Vector2d(-mouseX + pmouseX, -mouseY + pmouseY));
                if (draggingLine) {
                    tempLine.pB.pos = inverseTransform(new Vector2d(mouseX, mouseY));
                    tempLine.draw();
                }
            } else if (mousePressed && (mousePressed != lastPressed) && mouseButton == PApplet.LEFT) {
                //We have a new click
                if (draggingLine) {
                    Point nearestPoint = tempLine.pA;
                    Vector2d pointScreen = transform(nearestPoint.pos);
                    if (pointScreen.distanceTo(new Vector2d(mouseX, mouseY)) > nearestPoint.pointRadius) {
                        realizeTempLine(pointType, lineType);
                        snappedToStartPoint = true;
                        draggingLine = true;
                    } else {
                        //lineStart = new com.rgbgame.editor.Vector2d(mouseX,mouseY);
                        //tempLine.pA.pos = lineStart;
                        clearDraggedLine();
                        //snappedToStartPoint = true;
                        //draggingLine = false;
                    }
                } else {
                    draggingLine = true;
                    Vector2d mouseWorld = inverseTransform(new Vector2d(mouseX, mouseY));
                    Point nearestPoint = getPointByLocation(mouseWorld);
                    if (nearestPoint != null) {
                        Vector2d pointScreen = transform(nearestPoint.pos);
                        if (pointScreen.distanceTo(new Vector2d(mouseX, mouseY)) < nearestPoint.pointRadius) {
                            snappedToStartPoint = true;
                            snappedStartPoint = nearestPoint;
                            tempLine.pA = snappedStartPoint;
                        } else {
                            snappedToStartPoint = false;
                            lineStart = mouseWorld;
                            tempLine.pA.pos = lineStart;
                        }
                    } else {
                        snappedToStartPoint = false;
                        lineStart = mouseWorld;
                        tempLine.pA.pos = lineStart;
                    }
                }
                //println("Starting line");
            } else if (draggingLine) {
                Vector2d mouseWorld = inverseTransform(new Vector2d(mouseX, mouseY));
                Point nearestPoint = getPointByLocation(mouseWorld);
                if (nearestPoint != null) {
                    Vector2d pointScreen = transform(nearestPoint.pos);
                    if (pointScreen.distanceTo(new Vector2d(mouseX, mouseY)) < nearestPoint.pointRadius) {
                        snappedToEndPoint = true;
                        snappedEndPoint = nearestPoint;
                        tempLine.pB.pos = new Vector2d(nearestPoint.pos.x, nearestPoint.pos.y);
                    } else {
                        snappedToEndPoint = false;
                        tempLine.pB.pos = inverseTransform(new Vector2d(mouseX, mouseY));
                    }
                } else {
                    snappedToEndPoint = false;
                    tempLine.pB.pos = inverseTransform(new Vector2d(mouseX, mouseY));
                }

                tempLine.draw();
                if (snappedToStartPoint) {
                    pApplet.fill(255);
                }
                tempLine.pA.draw();
                if (snappedToEndPoint) {
                    pApplet.fill(255);
                } else {
                    pApplet.noFill();
                }
                tempLine.pB.draw();
                pApplet.noFill();
                //println("Drawing line from "+tempLine.pA.pos.x+","+tempLine.pA.pos.y+" to "+tempLine.pB.pos.x+","+tempLine.pB.pos.y);
            }
        }
        lastPressed = mousePressed;
        lastButton = mouseButton;

    }

    public void realizeTempLine(int pointType, int lineType) {
        Point pA;
        Point pB;
        //Turns the temporary line being drawn into a permanent one
        if (!draggingLine) {
            return;
        }
        if (snappedToStartPoint) {
            pA = snappedStartPoint;
            tempLine.pA = new Point(lineStart, TEMP);
            snappedToStartPoint = false;
            if (toolType == TOOL_LINE_STRIP) lineStart.set(tempLine.pB.pos.x, tempLine.pB.pos.y);
            //println("Hello");
        } else {
            pA = new Point(new Vector2d(tempLine.pA.pos.x, tempLine.pA.pos.y), pointType);
        }
        if (snappedToEndPoint) {
            pB = snappedEndPoint;
            tempLine.pB = new Point(lineStart, TEMP);
            snappedToEndPoint = false;
        } else {
            pB = new Point(new Vector2d(tempLine.pB.pos.x, tempLine.pB.pos.y), pointType);
        }
        if (pA == pB) {
            draggingLine = false;
            return;
        }
        if (transform(pA.pos).distanceTo(transform(pB.pos)) < pA.pointRadius) {
            if (toolType != TOOL_LINE_STRIP) {
                draggingLine = false;
                //return;
            }
            return;
        }
        draggingLine = false;
        if (toolType == TOOL_LINE_STRIP || toolType == TOOL_PENCIL) {
            tempLine.pA = pB;
            lineStart.set(tempLine.pB.pos.x, tempLine.pB.pos.y);
            tempLine.pB = new Point(lineStart, TEMP);
            draggingLine = true;
            snappedToStartPoint = true;
            snappedStartPoint = tempLine.pA;
        }
        addLine(pA, pB, lineType);
        //draggingLine = false;
    }

    int focusLock;
    //		NONE = 0
    public static final int EDITOR = 2;
    public static final int DOCK = 1;


    public void setCursor() {
//			ARROW, CROSS, HAND, MOVE, TEXT, or WAIT
//			These mappings don't look so hot right now, at least on OS X.
//			May want to provide own graphics if they work in browser.
        /*if(lastTool == toolType){return;}
      switch(toolType){
        case TOOL_NONE:
          cursor(ARROW);
          break;
        case TOOL_HAND:
          cursor(HAND);
          break;
        case TOOL_MAG:
          cursor(MOVE);
          break;
        case TOOL_LINE:
          cursor(CROSS);
          break;
        case TOOL_ERASER:
          cursor(ARROW);
          break;
        case TOOL_POINT:
          cursor(CROSS);
          break;
        case TOOL_PENCIL:
          cursor(ARROW);
          break;
      }
      lastTool = toolType;*/
    }


    public boolean UITrap() {
        //Returns true if the UI wants to block input to the editor
        if (focusLock == EDITOR) {
            return false;
        }
        return myDock.UITrap;
    }

    boolean draggingLine;
    Vector2d lineStart;
    Line tempLine;
    boolean snappedToStartPoint;
    boolean snappedToEndPoint;
    Point snappedStartPoint;
    Point snappedEndPoint;

    float UIHeight;
    float UIWidth;

    UIDock myDock;
    ArrowWidget myArrow;
    PencilWidget myPencil;
    LineWidget myLine;
    ZoomWidget myZoom;
    ConnectedLineWidget myConnectedLine;
    PointWidget myPointWidget;
    EraserWidget myEraser;
    OpenWidget myOpen;
    SaveWidget mySave;
    PFont font;

    public void clearDraggedLine() {
        draggingLine = false;
        snappedToStartPoint = false;
        snappedToEndPoint = false;
        lineStart = new Vector2d(0, 0);
        tempLine = new Line(pApplet, new Point(lineStart, TEMP), new Point(lineStart, TEMP), TEMP);
    }

    public void UIInit() {
        font = pApplet.loadFont("LucidaGrande-Bold-14.vlw");
        pApplet.textFont(font, 14);
        UIHeight = pApplet.height * .8f;
        UIWidth = 45.0f;
        clearDraggedLine();

        myDock = new UIDock(this);
        //for (int i=0; i<4; i++){
        myArrow = new ArrowWidget(pApplet, myDock);
        myDock.addWidget(myArrow);
        myPencil = new PencilWidget(pApplet, myDock);
        myDock.addWidget(myPencil);
        myLine = new LineWidget(pApplet, myDock);
        myDock.addWidget(myLine);
        myConnectedLine = new ConnectedLineWidget(pApplet, myDock);
        myDock.addWidget(myConnectedLine);
        myPointWidget = new PointWidget(pApplet, myDock);
        myDock.addWidget(myPointWidget);
        myZoom = new ZoomWidget(pApplet, myDock);
        myDock.addWidget(myZoom);
        myEraser = new EraserWidget(pApplet, myDock);
        myDock.addWidget(myEraser);
        myOpen = new OpenWidget(pApplet, myDock);
        myDock.addWidget(myOpen);
        mySave = new SaveWidget(pApplet, myDock);
        myDock.addWidget(mySave);

        //}
        myDock.go();

    }

    public void UIDraw() {
        myDock.drawDock();
        /*stroke(160,160,180);
        fill(255,255,255,100);
        rect(0,height/2.0-UIHeight/2.0,UIWidth,UIHeight);
        noFill();*/
        Vector2d mouseWorld = inverseTransform(new Vector2d(pApplet.mouseX, pApplet.mouseY));
        if (mouseWorld != null) {
            pApplet.fill(255);
            pApplet.text(String.valueOf(Math.round(mouseWorld.x)) + "," + String.valueOf(Math.round(mouseWorld.y)), 0, 15);
            pApplet.noFill();
        }
        String pT;
        String pC;
        String wT;
        String wC;
        if (pointType == WALL) {
            pT = "Wall";
        } else if (pointType == PARTICLE) {
            pT = "Particle";
        } else if (pointType == PINPARTICLE) {
            pT = "Pinned Particle";
        } else {
            pT = "None";
        }
        if (pointColor == RED) {
            pC = "Red";
        } else if (pointColor == GREEN) {
            pC = "Green";
        } else if (pointColor == BLUE) {
            pC = "Blue";
        }
        //else if (pointColor == INVISIBLE){ ; } //if invisible lines, keep last particle color
        else {
            pC = "None";
        }
        if (lineType == WALL) {
            if (wallType == WALL) wT = "Wall";
            else if (wallType == ICEWALL) wT = "Ice Wall";
            else if (wallType == DEATHWALL) wT = "Death Wall";
            else if (wallType == STICKYWALL) wT = "Sticky Wall";
            else wT = "Wall";
        } else if (lineType == STRUT) {
            wT = "Strut";
        } else if (lineType == SOLIDSTRUT) {
            wT = "Solid Strut";
        } else {
            wT = "None";
        }
        if (lineType == SOLIDSTRUT) {
            wC = "Gray";
        } else if (lineColor == RED) {
            wC = "Red";
        } else if (lineColor == GREEN) {
            wC = "Green";
        } else if (lineColor == BLUE) {
            wC = "Blue";
        } else if (lineColor == INVISIBLE) {
            wC = "Invisible";
        } else {
            wC = "None";
        }
        pApplet.text("Point type: " + pT + ((lineType != WALL) ? "; color: " + pC : ""), 0, pApplet.height - 50);
        pApplet.text("Line type: " + wT + ((lineType != WALL) ? "; color: " + wC : ""), 0, pApplet.height - 25);
    }



    public Vector2d transform(float _x, float _y) {
        return transform(new Vector2d(_x, _y));
    }

    public Vector2d transform(Vector2d vec) {
        //Transforms world coordinates into screen coordinates
        return myView.worldToScreen(vec);
    }

    public Vector2d inverseTransform(Vector2d vec) {
        //Transforms screen coordinates into world coordinates (mostly for mouse input stuff)
        //Should be the mathematical inverse of transform
        return myView.screenToWorld(vec);
    }

    public class FileHandler {

        // Use 'makeLevel()' to do the lifting

        public String currentFile;
        public String inputString;
        public String headerString;
        private final rgbEditor editor;

        public FileHandler(rgbEditor parent) {
            currentFile = "";
            editor = parent;
            inputString = "";
            headerString = "";
        }

        public FileHandler(rgbEditor parent, String levelString, boolean levelFromFile) {
            editor = parent;
            inputString = "";
            headerString = "";
            if(levelFromFile) {
                currentFile = levelString;
                makeLevel(currentFile);
            }
            else {
                makeLevelFromString(levelString);
            }
        }

        public String printHeader() {
            System.out.println(headerString);
            return headerString;
        }

        /*
               * Simply returns a string containing the contents of the
               * level specified by filename, with comments removed.
               *
               * Returns an empty file if no file found.
               */
        private String loadLevel(String filename) {
            String[] buff;
            try {
                buff = pApplet.loadStrings(filename);
            } catch (Exception e) {
                System.out.println("Could not find " + filename);
                return "";
            }

            String returnValue = "";
            for (int i = 0; i < buff.length; i++) {

                if (!(buff[i].length() > 1 && buff[i].charAt(0) == '/' && buff[i]
                        .charAt(1) == '/'))
                    returnValue += buff[i] + "\n";
            }
            println("Level loaded, beginning parse.");
            return returnValue;
        }

        public void makeLevel(String filename) {
            println("Loading " + filename);
            parseLevelString(loadLevel(filename));
            println(filename + " parsed");
            //numWalls = myWall.size(); // myWall is list of all walls
            //levelString = "";
        }

        public void makeLevelFromString(String toParse) {
            parseLevelString(toParse);
            //numWalls = myWall.size();
            //levelString = "";
        }

        public void parseLevelString(String levelString) {
            parseHeader(levelString);
            println("Header parsed");
            parseParticles(levelString);
            println("Particles parsed");
            parseWalls(levelString);
            println("Walls parsed");
            parseStruts(levelString);
            println("Struts parsed");
        }
        
        public void parseHeader(String levelString) {
            // Parse all the crap that doesn't need its own section, like one-off
            // variables and such.
            // This would be the place to add other user-tunable parameters.
            // For some reason the player is also initialized here...I suck.
            String[] newHeaders = levelString.split("headerBegin\n");
            if (newHeaders.length > 1) {
                String[] newHeaders2 = newHeaders[1].split("headerEnd");
                headerString = "headerBegin\n" + newHeaders2[0] + "headerEnd\n";
                //return;
            }
            
            if (newHeaders.length > 1) {
                newHeaders = newHeaders[1].split("headerEnd");
                String[] headerList = newHeaders[0].split("\n");
                //int numHs = headerList.length;

                for (int i = 0; i < headerList.length; i++) {
                	String[] currentHeader = headerList[i].split(",");
                    String toSet = currentHeader[0];
                    try{
                    float parameter = Float.parseFloat(currentHeader[1]);
                    if (toSet.equals("GRAVITY")) {
                        gravity = parameter;
                    } else if (toSet.equals("GOALX")) {
                        goalX = parameter;
                    } else if (toSet.equals("GOALY")) {
                        goalY = parameter;
                    } else if (toSet.equals("PLAYERX")) {
                        playerX = parameter;
                    } else if (toSet.equals("PLAYERY")) {
                        playerY = parameter;
                    } else if (toSet.equals("SPRINGCONSTANT")) {
                        springConstant = parameter;
                    } else if (toSet.equals("MAXANGULARVELOCITY")) {
                        maxAngularVelocity = parameter;
                    } else if (toSet.equals("MAXABSSPIN")) {
                    	maxAbsSpin = parameter;
                    } else if (toSet.equals("ANGULARACCELERATION")) {
                    	angularAcceleration = parameter;
                    }
                    } catch (Exception e) {
                    	continue;
                    }

                }
            }
        }
        
        public String generateHeader() {
        	String newHeader = "headerBegin";
        	newHeader += "\nGRAVITY,"+gravity;
        	newHeader += "\nGOALX,"+goalX;
        	newHeader += "\nGOALY,"+goalY;
        	newHeader += "\nPLAYERX,"+playerX;
        	newHeader += "\nPLAYERY,"+playerY;
        	newHeader += "\nSPRINGCONSTANT,"+springConstant;
        	newHeader += "\nMAXANGULARVELOCITY,"+maxAngularVelocity;
        	newHeader += "\nMAXABSSPIN,"+maxAbsSpin;
        	newHeader += "\nANGULARACCELERATION,"+angularAcceleration;
        	newHeader += "\nheaderEnd\n";
        	headerString = newHeader;
        	return newHeader;
        }

        public void parseParticles(String levelString) {
            String[] newParticles = levelString.split("particlesBegin\n");
            if (newParticles.length > 1) {
                newParticles = newParticles[1].split("particlesEnd");
                String[] particleList = newParticles[0].split("\n");
                for (int i = 0; i < particleList.length; i++) {
                    String[] currentParticle = particleList[i].split(",");
                    if (currentParticle.length < 5) continue;
                    float xCoord = Float.parseFloat(currentParticle[1]);
                    float yCoord = Float.parseFloat(currentParticle[2]);
                    float pMass = Float.parseFloat(currentParticle[3]);
                    float pRad = Float.parseFloat(currentParticle[4]);
                    int pCol;
                    if (currentParticle[5].equals("RED")) {
                        pCol = RED;//new Vector3d(255, 0, 0);
                    } else if (currentParticle[5].equals("GREEN")) {
                        pCol = GREEN;//new Vector3d(0, 255, 0);
                    } else if (currentParticle[5].equals("BLUE")) {
                        pCol = BLUE;//new Vector3d(0, 0, 255);
                    } else if (currentParticle[5].equals("INVISIBLE")) {
                        pCol = INVISIBLE;
                    } else {
                        pCol = NONE;
                    }
                    pointColor = pCol;
                    if (currentParticle[0].equals("pinParticle")) {
                        pointType = PINPARTICLE;
                    } else {
                        pointType = PARTICLE;
                    }

                    //TODO: add mass/radius info to particle

                    addPoint(xCoord, yCoord, pointType);
                }
            }
        }

        public void parseStruts(String levelString) {
            String[] newStruts = levelString.split("strutsBegin\n");
            if (newStruts.length > 1) {
                if (newStruts[1].startsWith("strutsEnd")) return;
                newStruts = newStruts[1].split("strutsEnd");
                String[] strutList = newStruts[0].split("\n");
                for (int i = 0; i < strutList.length; i++) {
                    String[] currentStrut = strutList[i].split(",");
                    //if (currentStrut.length < 4) continue;
                    //Particle partA = (Particle) otherParticle
                    //.get(parseInt(currentStrut[1]));
                    //Particle partB = (Particle) otherParticle
                    //.get(parseInt(currentStrut[2]));
                    Point pointA = getPointByIndex(Integer.parseInt(currentStrut[1]));
                    Point pointB = getPointByIndex(Integer.parseInt(currentStrut[2]));
                    int pCol;
                    boolean colored = false;
                    boolean drawn = true;
                    boolean colliding = false;
                    if (currentStrut[3].equals("RED")) {
                        pCol = RED;
                        colored = true;
                    } else if (currentStrut[3].equals("GREEN")) {
                        pCol = GREEN;
                        colored = true;
                    } else if (currentStrut[3].equals("BLUE")) {
                        pCol = BLUE;
                        colored = true;
                    } else if (currentStrut[3].equals("INVISIBLE")) {
                        pCol = INVISIBLE;
                        drawn = false;
                    } else if (currentStrut[3].equals("COLLIDING")) {
                        pCol = NONE;
                        colored = false;
                        colliding = true;
                    } else {
                        pCol = NONE;
                    }

                    //Now set lineType
                    lineColor = pCol;
                    if (colliding) lineType = SOLIDSTRUT;
                    else lineType = STRUT;
                    addLine(pointA, pointB, lineType);
                }
            }
        }

        public void parseWalls(String levelString) {
            {
                String[] wallPaths = levelString.split("wallBegin\n");
                for (int j = 0; j < wallPaths.length; j++) {
                    String[] commandList = wallPaths[j].split("\n");
                    // println(levelString);
                    int numWallPts = 0;
                    for (int i = 0; i < commandList.length; i++) {
                        if (commandList[i].split(",")[0].equals("wallPoint")) {
                            numWallPts++;
                        }
                        if (commandList[i].split(",")[0].equals("wallEnd")) {
                            i = commandList.length;
                        }
                    }

                    Vector2d[] wallVecs = new Vector2d[numWallPts];
                    int wallIndex = 0;
                    for (int i = 0; i < commandList.length; i++) {
                        String[] currentCommand = commandList[i].split(",");

                        // println(currentCommand[0]);
                        if (currentCommand[0].equals("wallPoint")) {

                            float xCoord = Float.parseFloat(currentCommand[1]);
                            float yCoord = Float.parseFloat(currentCommand[2]);
                            wallVecs[wallIndex] = new Vector2d(xCoord, yCoord);
                            wallIndex++;
                        }
                        if (currentCommand[0].equals("wallEnd")) {
                            i = commandList.length;
                        }
                    }
                    wallList(wallVecs);
                }
            }

            {// iceWall segment
                String[] wallPaths = levelString.split("iceWallBegin\n");
                for (int j = 0; j < wallPaths.length; j++) {
                    String[] commandList = wallPaths[j].split("\n");
                    // println(levelString);
                    int numWallPts = 0;
                    for (int i = 0; i < commandList.length; i++) {
                        if (commandList[i].split(",")[0].equals("iceWallPoint")) {
                            numWallPts++;
                        }
                        if (commandList[i].split(",")[0].equals("iceWallEnd")) {
                            i = commandList.length;
                        }
                    }

                    Vector2d[] wallVecs = new Vector2d[numWallPts];
                    int wallIndex = 0;
                    for (int i = 0; i < commandList.length; i++) {
                        String[] currentCommand = commandList[i].split(",");
                        // println(currentCommand[0]);
                        if (currentCommand[0].equals("iceWallPoint")) {
                            float xCoord = Float.parseFloat(currentCommand[1]);
                            float yCoord = Float.parseFloat(currentCommand[2]);
                            wallVecs[wallIndex] = new Vector2d(xCoord, yCoord);
                            wallIndex++;
                        }
                        if (currentCommand[0].equals("iceWallEnd")) {
                            i = commandList.length;
                        }
                    }
                    iceWallList(wallVecs);
                }
            }

            {// deathWall segment
                String[] wallPaths = levelString.split("deathWallBegin\n");
                for (int j = 0; j < wallPaths.length; j++) {
                    String[] commandList = wallPaths[j].split("\n");
                    // println(levelString);
                    int numWallPts = 0;
                    for (int i = 0; i < commandList.length; i++) {
                        if (commandList[i].split(",")[0].equals("deathWallPoint")) {
                            numWallPts++;
                        }
                        if (commandList[i].split(",")[0].equals("deathWallEnd")) {
                            i = commandList.length;
                        }
                    }

                    Vector2d[] wallVecs = new Vector2d[numWallPts];
                    int wallIndex = 0;
                    for (int i = 0; i < commandList.length; i++) {
                        String[] currentCommand = commandList[i].split(",");
                        // println(currentCommand[0]);
                        if (currentCommand[0].equals("deathWallPoint")) {
                            float xCoord = Float.parseFloat(currentCommand[1]);
                            float yCoord = Float.parseFloat(currentCommand[2]);
                            wallVecs[wallIndex] = new Vector2d(xCoord, yCoord);
                            wallIndex++;
                        }
                        if (currentCommand[0].equals("deathWallEnd")) {
                            i = commandList.length;
                        }
                    }
                    deathWallList(wallVecs);
                }
            }
            {// stickyWall segment
                String[] wallPaths = levelString.split("stickyWallBegin\n");
                for (int j = 0; j < wallPaths.length; j++) {
                    String[] commandList = wallPaths[j].split("\n");
                    // println(levelString);
                    int numWallPts = 0;
                    for (int i = 0; i < commandList.length; i++) {
                        if (commandList[i].split(",")[0].equals("stickyWallPoint")) {
                            numWallPts++;
                        }
                        if (commandList[i].split(",")[0].equals("stickyWallEnd")) {
                            i = commandList.length;
                        }
                    }

                    Vector2d[] wallVecs = new Vector2d[numWallPts];
                    int wallIndex = 0;
                    for (int i = 0; i < commandList.length; i++) {
                        String[] currentCommand = commandList[i].split(",");
                        // println(currentCommand[0]);
                        if (currentCommand[0].equals("stickyWallPoint")) {
                            float xCoord = Float.parseFloat(currentCommand[1]);
                            float yCoord = Float.parseFloat(currentCommand[2]);
                            wallVecs[wallIndex] = new Vector2d(xCoord, yCoord);
                            wallIndex++;
                        }
                        if (currentCommand[0].equals("stickyWallEnd")) {
                            i = commandList.length;
                        }
                    }
                    stickyWallList(wallVecs);
                }
            }
        }

        public void wallList(Vector2d[] verts) {
            if (verts.length > 0) {
                lineType = WALL;
                pointType = WALL;
                for (int i = 1; i < verts.length; i++) {
                    Vector2d posA = new Vector2d(verts[i - 1].x, verts[i - 1].y);
                    Point pA = getPointByLocation(posA);
                    if(pA == null || posA.distanceTo(pA.pos) > .01) {
                        pA = new Point(posA, WALL);
                        addPoint(pA);
                    }
                    Vector2d posB = new Vector2d(verts[i].x, verts[i].y);
                    Point pB = getPointByLocation(posA);
                    if(pB == null || posB.distanceTo(pB.pos) > .01) {
                        pB = new Point(posB, WALL);
                        addPoint(pB);
                    }
                    addLine(pA, pB, lineType);
                }
            }
        }

        public void iceWallList(Vector2d[] verts) {
            if (verts.length > 0) {
                lineType = ICEWALL;
                pointType = ICEWALL;
                for (int i = 1; i < verts.length; i++) {
                    Vector2d posA = new Vector2d(verts[i - 1].x, verts[i - 1].y);
                    Point pA = getPointByLocation(posA);
                    if (pA == null || posA.distanceTo(pA.pos) > .01) {
                        pA = new Point(posA);
                        addPoint(pA);
                    }
                    Vector2d posB = new Vector2d(verts[i].x, verts[i].y);
                    Point pB = getPointByLocation(posB);
                    if (pB == null || posB.distanceTo(pB.pos) > .01) {
                        pB = new Point(posB);
                        addPoint(pB);
                    }
                    addLine(pA, pB, lineType);
                }
            }
        }

        public void deathWallList(Vector2d[] verts) {
            if (verts.length > 0) {
                lineType = DEATHWALL;
                pointType = DEATHWALL;
                for (int i = 1; i < verts.length; i++) {
                    Vector2d posA = new Vector2d(verts[i - 1].x, verts[i - 1].y);
                    Point pA = getPointByLocation(posA);
                    if (pA == null || posA.distanceTo(pA.pos) > .01) {
                        pA = new Point(posA);
                        addPoint(pA);
                    }
                    Vector2d posB = new Vector2d(verts[i].x, verts[i].y);
                    Point pB = getPointByLocation(posB);
                    if (pB == null || posB.distanceTo(pB.pos) > .01) {
                        pB = new Point(posB);
                        addPoint(pB);
                    }
                    addLine(pA, pB, lineType);
                }
            }
        }

        public void stickyWallList(Vector2d[] verts) {
            if (verts.length > 0) {
                lineType = STICKYWALL;
                pointType = STICKYWALL;
                for (int i = 1; i < verts.length; i++) {
                    Vector2d posA = new Vector2d(verts[i - 1].x, verts[i - 1].y);
                    Point pA = getPointByLocation(posA);
                    if (pA == null || posA.distanceTo(pA.pos) > .01) {
                        pA = new Point(posA);
                        addPoint(pA);
                    }
                    Vector2d posB = new Vector2d(verts[i].x, verts[i].y);
                    Point pB = getPointByLocation(posB);
                    if (pB == null || posB.distanceTo(pB.pos) > .01) {
                        pB = new Point(posB);
                        addPoint(pB);
                    }
                    addLine(pA, pB, lineType);
                }
            }
        }

    }

    boolean isChosen = false;
    boolean choosing = false;
    JFileChooser fc;
    boolean choosingSave = false;
    boolean saveChosen = false;
    String inputPath;
    String outputPath;

    File file;
    File ofile;

    //loads close column from chosen file into closePrices[] array
    public void chooseFile() {
        //println(choosing);
        choosing = true;
        pApplet.noLoop();
        file = null;
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                try {
                    file = pApplet.inputFile("Please select a file to load.");
                    if (file == null) {
                        choosing = false;
                        pApplet.mousePressed = false;
                        focusLock = NONE;
                        pApplet.loop();
                        return;
                    }
                    inputPath = file.getAbsolutePath();
                    customInit(file.getAbsolutePath(), true);
                } catch (Exception e) {
                }
                choosing = false;
                pApplet.mousePressed = false;
                focusLock = NONE;
                pApplet.loop();
            }
        }
        );
    }

    public void chooseSaveFile() {
        choosingSave = true;
        pApplet.noLoop();
        ofile = null;
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                try {
                    ofile = pApplet.outputFile("Please select a location to save to.");

                    if (ofile == null) {
                        choosing = false;
                        pApplet.mousePressed = false;
                        focusLock = NONE;
                        pApplet.loop();
                        return;
                    }

                    outputPath = ofile.getAbsolutePath();
                    choosingSave = false;
                    saveFile(outputPath);
                } catch (Exception e) {
                    ;
                }
                pApplet.mousePressed = false;
                focusLock = NONE;
                pApplet.loop();
            }
        }
        );

    }

    public void saveFile(String path) {
        String[] myS = new String[1];
        myS[0] = print();
        pApplet.saveStrings(path, myS);
    }


    static public void main(String args[]) {
			PApplet.main(new String[] { "com.rgbgame.editor.rgbEditor" });
		}
	}